package Uploader;

import java.io.Console;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;

import gnu.io.*; 
import DataFile.DataFileCreator;

public class Uploader {
	
	private enum UploaderCommand {
		NONE,
		GET_AVAILABLE_MEMORY, // returns the maximum amount of memory available for jvm programs
		UPLOAD_CODE
	}
	
	private enum UploaderReply {
		REPLY_SUCCESS,
		REPLY_FAILURE
	};
	
	public enum UploadResult {
		SUCCESS,
		COM_PORT_NOT_FOUND,
		COM_PORT_IN_USE,
		IO_EXCEPTION,
		GET_AVAILABLE_MEMORY_FAILED,
		DEVICE_MEMORY_TO_SMALL_FOR_DATA,
		UPLOAD_CODE_COMMAND_FAILED,
		UPLOAD_CODE_SEND_PARAMETERS_FAILED,
		UPLOAD_CODE_SEND_DATA_FAILED,
		UNSUPPORTED_COM_OPERATION_EXCEPTION
	}
	
	private static class UploadParameters {
		
		public UploadParameters(int size, int checksum){
			this.size = size;
			this.checksum = checksum;
		}
		
		public int size;
		public int checksum;
	};
	
	private static final int SerialBaudrate = 115200;
	
	
	public static UploadResult Upload(byte[] data, String serialPortName){

	    Enumeration enumComm = CommPortIdentifier.getPortIdentifiers();
	    
	    while (enumComm.hasMoreElements()) {
	    	
	    	SerialPort uploadPort = null;
	    	InputStream uploadInput = null;
	    	OutputStream uploadOutput = null;
	    	
	    	CommPortIdentifier serialPortId = (CommPortIdentifier) enumComm.nextElement();
	     	if(serialPortId.getPortType() == CommPortIdentifier.PORT_SERIAL &&
	     	   serialPortId.getName().compareTo(serialPortName) == 0
	     	) {
	     		try{
	     			uploadPort = (SerialPort)serialPortId.open("upload", 100);	
	     			uploadPort.setSerialPortParams(
	     					SerialBaudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE
     				);
	     			
	     			
	     			
					uploadInput = uploadPort.getInputStream();
	     			uploadOutput = uploadPort.getOutputStream();
	     			
	     			//
	     			// fetch available memory size
	     			//
	     			uploadOutput.write(UploaderCommand.GET_AVAILABLE_MEMORY.ordinal());	     			
	     			int result = uploadInput.read();
	     			
	     			if(result != UploaderReply.REPLY_SUCCESS.ordinal()){
	     				System.err.println("GET_AVAILABLE_MEMORY failed.");
	     				return UploadResult.GET_AVAILABLE_MEMORY_FAILED;
	     			}
	     			
	     			// read memory size
	     			int memorySize = 
	     				(uploadInput.read() & 0xff) << 24 |
	     				(uploadInput.read() & 0xff) << 16 |
	     				(uploadInput.read() & 0xff) <<  8 |
	     				(uploadInput.read() & 0xff) <<  0;
	     			
	     			System.out.println("Gamepad memory size: " + memorySize);
	     			
	     			if(memorySize  < data.length){
	     				return UploadResult.DEVICE_MEMORY_TO_SMALL_FOR_DATA;
	     			}
	     			
	     			//
	     			// upload data
	     			//
	     			
	     			UploadParameters uploadParameters = new UploadParameters(
	     					data.length, 0 // TODO: add correct checksum
	     			);
	     			
	     			uploadOutput.write(UploaderCommand.UPLOAD_CODE.ordinal());	     			
	     			result = uploadInput.read();
	     			
	     			if(result != UploaderReply.REPLY_SUCCESS.ordinal()){
	     				System.err.println("UPLOAD_CODE failed.");
	     				return UploadResult.UPLOAD_CODE_COMMAND_FAILED;
	     			}
	     			
	     			//
	     			// send upload parameters
	     			//
	     			uploadOutput.write((uploadParameters.size >> 24) & 0xff);
	     			uploadOutput.write((uploadParameters.size >> 16) & 0xff);
	     			uploadOutput.write((uploadParameters.size >>  8) & 0xff);
	     			uploadOutput.write((uploadParameters.size >>  0) & 0xff);
	     			
	     			uploadOutput.write((uploadParameters.checksum >> 24) & 0xff);
	     			uploadOutput.write((uploadParameters.checksum >> 16) & 0xff);
	     			uploadOutput.write((uploadParameters.checksum >>  8) & 0xff);
	     			uploadOutput.write((uploadParameters.checksum >>  0) & 0xff);
	     			
	     			result = uploadInput.read();
	     			if(result != UploaderReply.REPLY_SUCCESS.ordinal()){
	     				System.err.println("UPLOAD_CODE_SEND_PARAMETERS failed.");
	     				return UploadResult.UPLOAD_CODE_SEND_PARAMETERS_FAILED;
	     			}
	     			
	     			//
	     			// send data
	     			//	     			
	     			uploadOutput.write(data);
	     			
	     			result = uploadInput.read();
	     			if(result != UploaderReply.REPLY_SUCCESS.ordinal()){
	     				System.err.println("UPLOAD_CODE_SEND_DATA failed.");
	     				return UploadResult.UPLOAD_CODE_SEND_DATA_FAILED;
	     			}
	     			
	     			
	     			//
	     			// shut down serial connection
	     			//
	     			
	     			uploadInput.close();
	     			uploadOutput.close();	     			
	     			uploadPort.close();
	     			
	     			return UploadResult.SUCCESS;
	     			
	     		} catch(PortInUseException e){
	     			return UploadResult.COM_PORT_IN_USE;
	     		} catch (IOException e2) {
	     			return UploadResult.IO_EXCEPTION;
	     		} catch (UnsupportedCommOperationException e3) {
					return UploadResult.UNSUPPORTED_COM_OPERATION_EXCEPTION;
				} finally {
					if(uploadInput != null){
						try {
							uploadInput.skip(uploadInput.available());
							uploadInput.close();
						} catch (IOException e) {
						}
					}
					if(uploadOutput != null){
						try {
							uploadOutput.flush();
							uploadOutput.close();
						} catch (IOException e) {
						}	 
					}
					if(uploadPort != null){
						uploadPort.close();
					}
				}
	    	}
	    }
	    
	    return UploadResult.COM_PORT_NOT_FOUND;
	}
	
}
